#include "Console.h"

Console::Console()
{
    screenName = "Console";
    visible = false;

    fontSize = 14; // Synonymous with height of line
    lineCount = 7;
    inputText = "";
    outputScroll = 0;
    previousInputPos = 0;
    font = ContentManager::LoadContent<Texture2D>("Media/DejaVuSansMonoLeft.tga");

    bgHeight = fontSize*lineCount;

    bgDisp = glGenLists(1);
    glNewList(bgDisp, GL_COMPILE);
        DrawHelper::Instance().drawRect2D(0, 0,
                                          Engine::Instance().getScreenWidth(),
                                          bgHeight,
                                          new Color(0.0f, 0.0f, 0.0f, 0.75f),
                                          true);
        DrawHelper::Instance().drawRect2D(0, bgHeight,
                                          Engine::Instance().getScreenWidth(),
                                          bgHeight + fontSize,
                                          new Color(0.25f, 0.25f, 0.25f, 0.75f),
                                          true);
    glEndList();

    SDL_EnableUNICODE(1);

    // Add default console methods
    addMethod(new SpecificConsoleMethod<Console>(this, &Console::help, "help"));
    addMethod(new SpecificConsoleMethod<Console>(this, &Console::quit, "quit"));
}

void Console::draw()
{
    glCallList(bgDisp);

    static int end = Engine::Instance().eventLog.size() - lineCount;
    if (end < 0)
    {
        end = 0;
        outputScroll = 0;
    }
    // Loop through and draw only the bottommost texts of output
    // (Careful when using an unsigned int and subtraction!)
    for (int i = Engine::Instance().eventLog.size() - 1; i >= end; i--)
    {
        // For debugging: std::cout << (i-outputScroll) << std::endl;
        DrawHelper::Instance().drawStr2D(Engine::Instance().eventLog[(i-outputScroll)], fontSize, 0,
                                         (lineCount*fontSize) - ((Engine::Instance().eventLog.size()-i)*fontSize), &White, font, -6);
    }
    DrawHelper::Instance().drawStr2D(inputText, fontSize, 0,
                                     bgHeight, &White, font, -6);
}

void Console::handleEvents(SDL_Event* event)
{
    if(event->type == SDL_KEYDOWN)
    {
        if(event->key.keysym.sym == SDLK_BACKQUOTE)
        {
            // Toggle visibility to control whether or not the console is open
            // and active. (The console will remain eventable so that it may
            // listen a console open command.)
            visible = !visible;
            if (visible)
            {
                updatable = true;
            }
            else
            {
                updatable = false;
            }
        }
        // If hidden, the console should only handle the open/close event. All
        // other events shall ignored until the console is visible and active.
        else if (!visible)
        {
            return ;
        }
        else if(event->key.keysym.sym == SDLK_PAGEUP)
        {
            //outputScroll++;
            // Don't allow scrolling too far
            // Note: Dealing with vector<>.size() as an unsigned integer can pose problems unless
            // the if statement is carefully constructed.
            //if (outputScroll + lineCount > Engine::Instance().eventLog.size())
            //{
            //    outputScroll = Engine::Instance().eventLog.size() - lineCount;
            //}
        }
        else if(event->key.keysym.sym == SDLK_PAGEDOWN)
        {
            if (outputScroll > 0)
                    outputScroll--;
        }
        // Add the entered character onto the input string
        // Note: The current font system can only render characters within ascii.
        // However, unicode makes it super easy to detect capitalisation.
        else if (event->key.keysym.unicode <= 255 && event->key.keysym.unicode >= 32)
        {
            inputText += (char)event->key.keysym.unicode;
            //inputText += (char)event->key.keysym.sym;
        }
        else
        {
            // Something else, determine
            switch(event->key.keysym.sym)
            {
                case SDLK_BACKSPACE:
                        inputText = inputText.substr(0,inputText.length()-1); // Remove last character
                    break;
                case SDLK_RETURN:
                    parseInput(&inputText);
                    previousInputPos = 0; // Reset position now that a new element has been added
                    outputScroll = 0; // Reset console scrolling so that the user can see the command they entered
                    inputText = ""; // Clear input box for next use
                    break;
                case SDLK_UP:
                    // Move up (and display) in the input command history
                    if (previousInput.size() > 0)
                    {
                        inputText = previousInput[(previousInput.size()-1-previousInputPos)];
                        if (previousInputPos < (previousInput.size()-1))
                            previousInputPos++;
                    }
                    break;
                case SDLK_DOWN:
                    // Move down (and display) in the input command history
                    if (previousInput.size() > 0)
                    {
                        if (previousInputPos > 0)
                            previousInputPos--;
                        inputText = previousInput[(previousInput.size()-1-previousInputPos)];
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

void Console::parseInput(std::string* input)
{
    // Don't continue if only a blank message was sent.
    if (input->empty())
        return;

    // Add input to Engine's event log and keep a record of the input.
    previousInput.push_back(*input);
    Engine::Instance().log(*input);

    // First, split up the input by spaces to separate the method name and its arguments/parameters
    std::vector<std::string> params = splitStringBySpaces(input);
    // Make sure something was written
    if (params.size() > 0)
    {
        // Convert method name to lower case
        // (The first parameter (param[0]) is the method name
        std::transform(params[0].begin(), params[0].end(), params[0].begin(), ::tolower);
        // Now, go through the stack of methods, check that the method name
        // matches and fire that method, and send out the other parameters with it
        bool found = false;
        for (unsigned int i = 0; i < methods.size(); i++)
        {
            if (methods[i]->methodName == params[0])
            {
                // We found a match, remove the method name
                params.erase(params.begin(),params.begin()+1);
                // Call the function
                methods[i]->call(methods[i]->methodName, params);
                // Clear params
                params.erase(params.begin(),params.end());
                found = true;
            }
        }
        // Let user know command was not recognized
        if (!found)
        {
            Engine::Instance().log("Unknown command: " + (*input));
        }
    }
}

void Console::addMethod(ConsoleMethod* m)
{
    methods.push_back(m);
    /// TODO: Issue warning if duplicate method named exists
}

void Console::quit(std::string s, std::vector<std::string> args)
{
    Engine::Instance().quit();
}

void Console::help(std::string s, std::vector<std::string> args)
{
    Engine::Instance().log("Available Commands: ");
    for (unsigned int i = 1; i < methods.size(); i++)
    {
         Engine::Instance().log(methods[i]->methodName);
    }
}

std::vector<std::string> Console::splitStringBySpaces(std::string* s)
{
     std::string buf;
     std::stringstream ss(*s);
     std::vector<std::string> output;
     while (ss >> buf)
     {
          output.push_back(buf);
     }
     return output;
}
